package com.icehelper.hades.base.util;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.icehelper.hades.base.log.HadesLogger;
import org.springframework.cglib.beans.BeanCopier;

public class BeanHelper extends org.springframework.beans.BeanUtils{

	private static final HadesLogger log = HadesLogger.create();

	/**
	 * 将对象bean里的各个字段转化为Map<String, Object>。 bean为null，则返回空Map。
	 * final和static字段不会被收集。
	 */
	public static Map<String, Object> fieldMap( Object bean ){

		if( bean == null ){
			return Collections.emptyMap();
		}
		Collection<Field> fields = getFields( bean );
		Map<String, Object> map = new HashMap<>( fields.size() );
		for( Field f : fields ){
			map.put( f.getName(), readFieldQuietly( bean, f ) );
		}
		return map;
	}

	/**
	 * 将对象bean里的各个字段转化为Map<String, String> bean为null，则返回空Map。
	 * final和static字段不会被收集。
	 */
	public static Map<String, String> fieldStrMap( Object bean ){

		Map<String, Object> map = fieldMap( bean );
		if( Inspections.isEmpty( map ) ){
			return Collections.emptyMap();
		}

		Map<String, String> strMap = new HashMap<>( map.size() );
		map.forEach( ( k, v ) -> strMap.put( k, v == null ? "" : Objects.toString( v ) ) );
		return strMap;
	}

	/**
	 * 将属性中值为空白字符串的设置为null。只处理有getter和setter的属性。 例如：
	 * <pre>
	 *   {"a":"", "b":"  ", "c":null, "d":0}
	 *   会变成
	 *   {"a":null, "b":null, "c":null, "d":0}
	 * </pre>
	 */
	public static void blankFieldAsNull( Object bean ){

		if( bean == null ){
			return;
		}
		getFields( bean ).stream().filter( f -> {
			Object val = readFieldQuietly( bean, f );
			return val instanceof String && Inspections.isBlank( val.toString() );
		} ).forEach( f -> writeFieldQuietly( bean, f, null ) );
	}

	private static ConcurrentMap<Class, Collection<Field>> fieldsCache = new ConcurrentHashMap<>();

	/**
	 * 获取一个对象的属性集合。不包括final和static属性。
	 * 同时会收集父类的字段，直到父类是Object，如果字段重复，以子类为准。
	 * final和static字段不会被收集。
	 */
	public static Collection<Field> getFields( Object bean ){

		if( bean == null ){
			return Collections.emptyList();
		}
		return getFields( bean.getClass() );
	}

	/**
	 * 获取一个对象的属性集合。不包括final和static属性。
	 * 同时会收集父类的字段，直到父类是Object，如果字段重复，以子类为准。
	 * final和static字段不会被收集。
	 */
	public static Collection<Field> getFields( Class<?> clazz ){

		if( clazz == null ){
			return Collections.emptyList();
		}
		if( !fieldsCache.containsKey( clazz ) ){
			Map<String, Field> mpField = new HashMap<>( 64 );
			Class<?> cls = clazz;
			while( cls != Object.class ){
				Field[] arrField = cls.getDeclaredFields();
				if( Inspections.isNotEmpty( arrField ) ){
					Stream.of( arrField ).filter(
									f -> !Modifier.isFinal( f.getModifiers() ) && !Modifier.isStatic( f.getModifiers() ) ) // 过滤 final 和 static
							.filter( f -> !mpField.containsKey( f.getName() ) ) // 过滤mpField已包含的字段，即对于重复字段只保留子类的
							.forEach( f -> mpField.put( f.getName(), f ) );
				}
				cls = cls.getSuperclass();
			}
			fieldsCache.putIfAbsent( clazz, mpField.values() );
		}
		return fieldsCache.get( clazz );
	}

	/**
	 * 读取一个属性的值，自动处理异常。
	 */
	public static Object readFieldQuietly( Object o, Field f ){

		f.setAccessible( true );
		try{
			return f.get( o );
		}
		catch( IllegalAccessException e ){
			log.error( "read field value failed. field:{}, object:{}", f.getName(), o.getClass().getSimpleName(), e );
			return null;
		}
	}

	/**
	 * 设置一个属性的值，自动处理异常。
	 */
	public static void writeFieldQuietly( Object o, Field f, Object val ){

		f.setAccessible( true );
		try{
			f.set( o, val );
		}
		catch( IllegalAccessException e ){
			log.error( "write field value failed. field:{}, object:{}, value:{}", f.getName(), o.getClass().getSimpleName(), val, e );
		}
	}

	/**
	 * 判断actual对象是否包含expected对象的所有非空property，并且值相同。
	 * 非空property指的是值不是null。
	 *
	 * @param actual   被比较的对象，判断该对象是否包含expected的非空property
	 * @param expected 被包含的对象
	 * @return 是否包含
	 */
	public static <A, B extends A> boolean hasAllSolidProperties( B actual, A expected ){

		return hasAllSolidProperties( actual, expected, null );
	}

	/**
	 * 判断actual对象是否包含expected对象的所有非空property，并且值相同。
	 * 非空property指的是值不是null。
	 *
	 * @param actual        被比较的对象，判断该对象是否包含expected的非空property
	 * @param expected      被包含的对象
	 * @param excludeFields 排除property，该集合里的property将不被比较，直接跳过
	 * @return 是否包含
	 */
	public static <A, B extends A> boolean hasAllSolidProperties( B actual, A expected, Set<String> excludeFields ){

		if( excludeFields == null ){
			excludeFields = Collections.emptySet();
		}
		Map<String, Object> expectedFields = fieldMap( expected );
		Map<String, Object> actualFields = fieldMap( actual );
		for( Entry<String, Object> e : expectedFields.entrySet() ){
			if( excludeFields.contains( e.getKey() ) ){
				continue;
			}
			if( e.getValue() != null && !e.getValue().equals( actualFields.get( e.getKey() ) ) ){
				return false;
			}
		}
		return true;
	}

	/**
	 * 获取所有在expected对象和actual对象的相同属性中值不同的非空属性。
	 * 非空property指的是值不是null。
	 *
	 * @param actual   被比较的对象，判断该对象是否包含expected的非空property
	 * @param expected 被包含的对象
	 * @return 没被包含的属性集合
	 */
	public static <A, B extends A> List<Entry<String, Object>> getSolidDifferentProperties( B actual, A expected ){

		return getSolidDifferentProperties( actual, expected, null );
	}

	/**
	 * 获取所有在expected对象和actual对象的相同属性中值不同的非空属性。
	 * 非空property指的是值不是null。
	 *
	 * @param actual        被比较的对象，判断该对象是否包含expected的非空property
	 * @param expected      被包含的对象
	 * @param excludeFields 排除property，该集合里的property将不被比较，直接跳过
	 * @return 没被包含的属性集合
	 */
	public static <A, B extends A> List<Entry<String, Object>> getSolidDifferentProperties( B actual, A expected, Set<String> excludeFields ){

		if( excludeFields == null ){
			excludeFields = Collections.emptySet();
		}
		Map<String, Object> expectedFields = fieldMap( expected );
		Map<String, Object> actualFields = fieldMap( actual );
		Set<String> finalExcludeFields = excludeFields;
		return expectedFields.entrySet().stream().filter( e -> e.getValue() != null ).filter(
				e -> !finalExcludeFields.contains( e.getKey() ) ).filter( e -> !e.getValue().equals( actualFields.get( e.getKey() ) ) ).collect(
				Collectors.toList() );
	}

	private static final ConcurrentHashMap<String, BeanCopier> COPIER_CACHE = new ConcurrentHashMap<>();

	/**
	 * 拷贝source的字段值到target对象。只拷贝同名同类型的字段值。
	 */
	public static void copyFields( Object source, Object target ){

		if( source == null || target == null ){
			return;
		}
		String cacheKey = source.getClass().getName() + " to " + target.getClass().getName();
		if( !COPIER_CACHE.containsKey( cacheKey ) ){
			COPIER_CACHE.putIfAbsent( cacheKey, BeanCopier.create( source.getClass(), target.getClass(), false ) );
		}
		COPIER_CACHE.get( cacheKey ).copy( source, target, null );
	}

	public static <S, T> List<T> copyListProperties( List<S> sources, Supplier<T> target ){

		return copyListProperties( sources, target, null );
	}

	/**
	 * 使用场景：Entity、Bo、Vo层数据的复制，因为BeanUtils.copyProperties只能给目标对象的属性赋值，却不能在List集合下循环赋值，因此添加该方法
	 * 如：List<AdminEntity> 赋值到 List<AdminVo> ，List<AdminVo>中的 AdminVo 属性都会被赋予到值
	 * S: 数据源类 ，T: 目标类::new(eg: AdminVo::new)
	 */
	public static <S, T> List<T> copyListProperties( List<S> sources, Supplier<T> target, BeanUtilsCallBack<S, T> callBack ){

		List<T> list = new ArrayList<>( sources.size() );
		for( S source : sources ){
			T t = target.get();
			copyProperties( source, t );
			if( callBack != null ){
				// 回调
				callBack.callBack( source, t );
			}
			list.add( t );
		}
		return list;
	}

	@FunctionalInterface
	public interface BeanUtilsCallBack<S, T>{

		void callBack( S t, T s );
	}
}
